#include "fxcc/core/graph/opengl3/ImGuiGlfwApp.h"
#include "fxcc/core/graph/opengl3/Resources.h"
#include "fxcc/core/graph/common/Geometry.h"
#include "fxcc/core/graph/opengl3/ssao/Effect.h"
#include "fxcc/core/graph/opengl3/ssao/GBufferEffect.h"
#include "fxcc/core/graph/opengl3/ssao/BlurEffect.h"
#include "fxcc/core/graph/opengl3/ssao/LightingEffect.h"
#include "fxcc/imgui/Editor.h"

using namespace fxcc::platform;
using namespace fxcc::graph;
using namespace fxcc::graph::opengl3;
using namespace fxcc::imgui::graph::opengl3;
using BaseApp = fxcc::imgui::graph::opengl3::GlfwApp;

#undef main

struct SimpleApp : public BaseApp
{
public:
    fxcc::graph::common::Transform transform;
    fxcc::graph::common::Camera camera;
    std::shared_ptr<fxcc::graph::opengl3::Resources> resources;
    glm::vec4 color;

    // unsigned int gBuffer;
    // unsigned int gPosition, gNormal, gAlbedo;
    std::shared_ptr<fxcc::graph::opengl3::RenderTarget> m_GBuffer;
    std::shared_ptr<fxcc::graph::opengl3::RenderTarget> m_SSAORT;
    std::shared_ptr<fxcc::graph::opengl3::RenderTarget> m_SSAOBlurRT;

    std::shared_ptr<fxcc::graph::opengl3::ssao::Effect> m_SSAOEffect;
    std::shared_ptr<fxcc::graph::opengl3::ssao::BlurEffect> m_SSAOBlurEffect;
    std::shared_ptr<fxcc::graph::opengl3::ssao::GBufferEffect> m_GBufferEffect;
    std::shared_ptr<fxcc::graph::opengl3::ssao::LightingEffect> m_LightingEffect;
    
    SimpleApp(const fxcc::platform::common::App::Desc &desc) : BaseApp(desc), color(1.0)
    {
    }
    float ourLerp(float a, float b, float f)
    {
        return a + f * (b - a);
    }
    bool Init() override
    {
        if (!BaseApp::Init())
        {
            return false;
        }

        if (!InitResources())
        {
            return false;
        }

        return true;
    };

    fxcc::graph::opengl3::ssao::Effect::SSAOKernel ssaoKernel;
    std::shared_ptr<fxcc::graph::opengl3::Texture2D> m_NoiseTexture;
    std::shared_ptr<fxcc::graph::opengl3::Mesh> m_CubeMesh, m_QuadMesh;
    // lighting info
    // -------------
    glm::vec3 lightPos = glm::vec3(2.0, 4.0, -2.0);
    glm::vec3 lightColor = glm::vec3(0.2, 0.2, 0.7);
    fxcc::graph::common::Transform modelTransform, cubeTransform;

    bool InitResources()
    {
        auto &am = fxcc::AssertManager::Inst();

        modelTransform.m_Position = glm::vec3(0.0f, 0.5f, 0.0);
        modelTransform.m_Rotation = glm::vec3(-90.0f, 0.f, 0.f);
        modelTransform.m_Scale = glm::vec3(1.0f);

        cubeTransform.m_Position = glm::vec3(0.0, 7.0f, 0.0f);
        cubeTransform.m_Scale= glm::vec3(7.5f);

        camera.SetPosition(glm::vec3(0.0f, 0.f, 5.0f));
        camera.LookAt(glm::vec3(0.0f));
        int SCR_WIDTH = m_Desc.m_Size.x;
        int SCR_HEIGHT = m_Desc.m_Size.y;

        {
            auto geoData = fxcc::graph::common::Geometry::CreateBox();
            m_CubeMesh = std::make_shared<fxcc::graph::opengl3::Mesh2>(geoData);
        }
        {
            auto geoData = fxcc::graph::common::Geometry::CreateQuad();
            m_QuadMesh = std::make_shared<fxcc::graph::opengl3::Mesh2>(geoData);
        }
        glEnable(GL_DEPTH_TEST);

        m_GBufferEffect = std::make_shared<fxcc::graph::opengl3::ssao::GBufferEffect>();
        m_SSAOEffect = std::make_shared<fxcc::graph::opengl3::ssao::Effect>();
        m_SSAOBlurEffect = std::make_shared<fxcc::graph::opengl3::ssao::BlurEffect>();
        m_LightingEffect = std::make_shared<fxcc::graph::opengl3::ssao::LightingEffect>();

        // load models
        resources = std::make_shared<fxcc::graph::opengl3::Resources>();
        const std::string objPath = "backpack/backpack.obj";
        // const std::string objPath = "cirno.fbx";

        if (!resources->LoadScene(am.GetFile(am.GetFile(objPath))))
        {
            return false;
        };



        // generate sample kernel
        // ----------------------
        std::uniform_real_distribution<GLfloat> randomFloats(0.0, 1.0); // generates random floats between 0.0 and 1.0
        std::default_random_engine generator;

        for (unsigned int i = 0; i < 64; ++i)
        {
            glm::vec3 sample(randomFloats(generator) * 2.0 - 1.0, randomFloats(generator) * 2.0 - 1.0, randomFloats(generator));
            sample = glm::normalize(sample);
            sample *= randomFloats(generator);
            float scale = float(i) / 64.0f;

            // scale samples s.t. they're more aligned to center of kernel
            scale = ourLerp(0.1f, 1.0f, scale * scale);
            sample *= scale;
            ssaoKernel.value[i] = (sample);
        }

        // generate noise texture
        // ----------------------
        // std::vector<glm::vec3> ssaoNoise;
        glm::vec3 ssaoNoise[16];
        for (unsigned int i = 0; i < 16; i++)
        {
            glm::vec3 noise(randomFloats(generator) * 2.0 - 1.0, randomFloats(generator) * 2.0 - 1.0, 0.0f); // rotate around z-axis (in tangent space)
            ssaoNoise[i] = noise;
        }
        {
            fxcc::graph::common::Texture2DDesc1 texDesc;
            texDesc.m_Image.InitHD(4, 4, 3, 2);
            texDesc.m_Image.m_Data = &ssaoKernel.value[0];
            texDesc.m_Mipmap = false;
            texDesc.m_SamplerType = fxcc::graph::common::SamplerType::_sampler_2d_nearest_repeat;
            auto &rs = fxcc::graph::opengl3::RenderStates::Inst();
            m_NoiseTexture = std::make_shared<fxcc::graph::opengl3::Texture2D>(texDesc);
        };

        return true;
    };

    fxcc::imgui::Editor editor;

    void OnEdit() override
    {
        ImVec2 sz(400, 300);
        ImGui::Begin("G buffer");
        editor.ImageEditInst<fxcc::graph::opengl3::RenderTarget>("g-buffer", *m_GBuffer, sz);
        ImGui::End();

        ImGui::Begin("SSAO");
        editor.ImageEditInst<fxcc::graph::opengl3::RenderTarget>("ssao map", *m_SSAORT, sz);
        ImGui::End();

        ImGui::Begin("SSAOBlur");
        editor.ImageEditInst<fxcc::graph::opengl3::RenderTarget>("ssao blur", *m_SSAOBlurRT, sz);
        ImGui::End();
    }
    void OnDestroy() override
    {

        BaseApp::OnDestroy();
    }
    void OnFrameRender() override
    {
        glViewport(0, 0, m_Desc.m_Size.x, m_Desc.m_Size.y);

        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        m_GBuffer->Begin();
        m_GBufferEffect->Begin();
        m_GBufferEffect->SetCamera(camera);
        m_GBufferEffect->InstMesh(m_CubeMesh.get(), cubeTransform, true);

        for (auto &it : resources->m_MeshDatas)
        {
            auto dataIndex = it.first;
            auto fixedMesh = resources->m_FixedMeshes[dataIndex];
            m_GBufferEffect->InstMesh(fixedMesh.get(), modelTransform, false);
        }
        m_GBufferEffect->End();
        m_GBuffer->End();

        m_SSAORT->Begin();

        m_SSAOEffect->Begin();
        m_SSAOEffect->SetScreenSize(m_Desc.m_Size.x, m_Desc.m_Size.y);
        m_SSAOEffect->SetKernel(ssaoKernel);
        // m_SSAOEffect->m_Shader->setMat4("projection", camera.GetProjection());
        m_SSAOEffect->SetProjection(camera.GetProjection());
        m_SSAOEffect->SetViewFragPosMap(m_GBuffer->m_Textures[0].get());
        m_SSAOEffect->SetViewNormalMap(m_GBuffer->m_Textures[1].get());
        // m_GBuffer->m_Textures[0]->Active(0);
        // m_GBuffer->m_Textures[1]->Active(1);
        // m_NoiseTexture->Active(2);
        m_SSAOEffect->SetNoiseTexture(m_NoiseTexture.get());
        {
            fxcc::graph::common::Transform2D transform;
            glm::vec2 pos = m_Desc.m_Size;
            transform.m_Pos = glm::vec3(pos / 2.0f, 0.f);;
            transform.m_Size = m_Desc.m_Size;
            m_SSAOEffect->SetTransform2D(transform);
        }
        m_SSAOEffect->DebugQuad2();
        m_SSAOEffect->End();

        m_SSAORT->End();

        // glBindFramebuffer(GL_FRAMEBUFFER, ssaoBlurFBO);
        m_SSAOBlurRT->Begin();
        // glClear(GL_COLOR_BUFFER_BIT);
        // shaderSSAOBlur->use();
        m_SSAOBlurEffect->Begin();
        // m_SSAORT->m_Textures[0]->Active(0);
        m_SSAOBlurEffect->SetSSAOInput(m_SSAORT->m_Textures[0].get());

        //glActiveTexture(GL_TEXTURE0);
        //glBindTexture(GL_TEXTURE_2D, ssaoColorBuffer);
        // renderQuad();
        m_SSAOBlurEffect->DebugQuad2();
        m_SSAOBlurEffect->End();
        m_SSAOBlurRT->End();

        glViewport(0, 0, m_Desc.m_Size.x, m_Desc.m_Size.y);
        glClear(GL_COLOR_BUFFER_BIT);

        m_LightingEffect->Begin();

        fxcc::graph::opengl3::ssao::LightingEffect::Light light;

        light.Position = glm::vec3(camera.GetViewMatrix() * glm::vec4(lightPos, 1.0));
        light.Color = lightColor;
        light.Linear = 0.09f;
        light.Quadratic = 0.032f;
        m_LightingEffect->SetLight(light);
        m_LightingEffect->SetScreenSize(m_Desc.m_Size.x, m_Desc.m_Size.y);
        
        // m_GBuffer->m_Textures[0]->Active(0);
        // m_GBuffer->m_Textures[1]->Active(1);
        // m_GBuffer->m_Textures[2]->Active(2);
        // m_SSAOBlurRT->m_Textures[0]->Active(3);
        m_LightingEffect->SetViewFragPosMap(m_GBuffer->m_Textures[0].get());
        m_LightingEffect->SetViewNormalMap(m_GBuffer->m_Textures[1].get());
        m_LightingEffect->SetAlbedoMap(m_GBuffer->m_Textures[2].get());
        m_LightingEffect->SetSSAOMap(m_SSAOBlurRT->m_Textures[0].get());
        {
            fxcc::graph::common::Transform2D transform;

            glm::vec2 pos = m_Desc.m_Size;

            transform.m_Pos = glm::vec3(pos / 2.0f, 0.f);;
            transform.m_Size = m_Desc.m_Size;
            m_LightingEffect->SetTransform2D(transform);

        }

        m_LightingEffect->DebugQuad2();
        m_LightingEffect->End();

    };

    void OnFramebuffersize(int w, int h) override
    {
        opengl3::GlfwApp::OnFramebuffersize(w, h);
        camera.SetAspect(m_Desc.Aspect());

        int SCR_WIDTH = w;;
        int SCR_HEIGHT = h;

        fxcc::graph::common::RenderTargetDesc1 desc1;
        desc1.m_TextureDescs.resize(4);
        {
            auto& texDesc = desc1.m_TextureDescs[0];
            texDesc.m_Image.InitHD(SCR_WIDTH, SCR_HEIGHT, 3, true);
            texDesc.m_Mipmap = false;
            texDesc.m_SamplerType = fxcc::graph::common::SamplerType::_sampler_2d_nearest;
        }
        {
            auto& texDesc = desc1.m_TextureDescs[1];
            texDesc.m_Image.InitHD(SCR_WIDTH, SCR_HEIGHT, 3, true);
            texDesc.m_Mipmap = false;
            texDesc.m_SamplerType = fxcc::graph::common::SamplerType::_sampler_2d_nearest_no_wrap;
        }

        {
            auto& texDesc = desc1.m_TextureDescs[2];
            texDesc.m_Image.InitHD(SCR_WIDTH, SCR_HEIGHT, 3, false);
            texDesc.m_Mipmap = false;
            texDesc.m_SamplerType = fxcc::graph::common::SamplerType::_sampler_2d_nearest_no_wrap;
        }
        {
            auto& texDesc = desc1.m_TextureDescs[3];
            texDesc.m_Image.InitHD(SCR_WIDTH, SCR_HEIGHT, 3, false);
            texDesc.m_Mipmap = false;
            texDesc.m_SamplerType = fxcc::graph::common::SamplerType::_sampler_2d_nearest_no_wrap;
        }
        
        {
            fxcc::graph::common::RenderTargetDesc rtDesc(desc1);
            rtDesc.m_AttachmentType = fxcc::graph::common::AttachmentType::_Depth24_Stencil8_;
            m_GBuffer = std::make_shared<fxcc::graph::opengl3::RenderTarget>(rtDesc);

        }

        {
            fxcc::graph::common::Texture2DDesc1 texDesc1;
            texDesc1.m_SamplerType = fxcc::graph::common::SamplerType::_sampler_2d_nearest_no_wrap;
            texDesc1.m_Mipmap = false;
            texDesc1.m_Image.InitHD(SCR_WIDTH, SCR_HEIGHT, 1, false);
            fxcc::graph::common::RenderTargetDesc1 rtDesc1({ texDesc1 });
            fxcc::graph::common::RenderTargetDesc rtDesc(rtDesc1);
            rtDesc.m_AttachmentType = fxcc::graph::common::AttachmentType::_Depth24_Stencil8_;

            m_SSAORT = std::make_shared<fxcc::graph::opengl3::RenderTarget>(rtDesc);
            m_SSAOBlurRT = std::make_shared<fxcc::graph::opengl3::RenderTarget>(rtDesc);

        }
    };


    void OnUpdate() override
    {
        opengl3::GlfwApp::OnUpdate();

        float ctTime = m_GlfwlTimer.m_CurrentTime;
        camera.FreeMove(m_Input, m_GlfwlTimer.m_DeltaTime);

        float deltaTime = m_GlfwlTimer.m_DeltaTime;
    }
};

int main()
{
    ztclog::pushFunction([](const ztclog::Line &line)
                         {
		if (line.m_Type != ztclog::Line::Type::_line_debug_)
		{
			std::cout << line.m_Text << std::endl;
		} });

    using _App = SimpleApp;
    using _Desc = fxcc::platform::common::App::Desc;

    _Desc desc;
    desc.m_Pos = glm::ivec2(100, 100);
    desc.m_Size = glm::ivec2(900, 600);
    desc.m_ScaleFactor = 1.0f;

    desc.m_Title = "test ssao";
    std::shared_ptr<_App> _app = std::make_shared<_App>(desc);
    return _app->Run();
};
